Repository navigation
Let spm deinit take an app back to CocoaPods - #59368
Conversation
Converting an app to SwiftPM with `--deintegrate` now moves the Podfile to Podfile.bak instead of deleting React Native lines from it (that edit left invalid Ruby for multi-line calls). `spm deinit` restores the Podfile, undoes the .gitignore lines the conversion wrote, removes empty ReactNativeSPM folders, and says to run `pod install`. The conversion refuses, before changing anything, when the Podfile has pods that are not React Native or when Podfile.bak already exists.
The Podfile is moved to Podfile.bak, so no pod is lost and `spm deinit` restores it. An explicit `--deintegrate` now lists the other pods, says CocoaPods will not install them while the app uses SwiftPM unless SwiftPM provides them, and continues. RNTester needs this: its Podfile has local example pods that SwiftPM provides. The automatic conversion of a new app still skips such Podfiles.
b75a2b8 to
dda80a2
Compare
cipolleschi
left a comment
There was a problem hiding this comment.
Thanks for this!
I'd changes some bits, to simplify the feature... See my comments!
| ``` | ||
|
|
||
| On a fresh CocoaPods app, this converts the app to SwiftPM. It runs | ||
| `pod deintegrate` and moves the `Podfile` to `Podfile.bak`. A Podfile with pods |
There was a problem hiding this comment.
Podfile can remain Podfile. Why do we need to change it?
There was a problem hiding this comment.
Done. The Podfile now stays as it is.
| `pod deintegrate` and moves the `Podfile` to `Podfile.bak`. A Podfile with pods | ||
| that are not part of React Native is not converted automatically; | ||
| `spm add --deintegrate` converts it and warns that those pods are not installed. | ||
| Without a Podfile, the CLI needs `@react-native-community/cli` 21 or newer to |
There was a problem hiding this comment.
if we keep it, we can lift this requirement
There was a problem hiding this comment.
Done. The CLI 21 requirement is gone, because the Podfile stays in place.
| 2. moves the `Podfile` to `Podfile.bak`, unchanged. `spm deinit` moves it back. | ||
| If `Podfile.bak` already exists, the command stops before it changes | ||
| anything. |
There was a problem hiding this comment.
I would not do this step.
There was a problem hiding this comment.
Done. The step is removed.
| Non-RN pods can stay side-by-side. Your Podfile still lists them. Remove the | ||
| leftover React Native lines (see step 2 above), then re-integrate the pods with | ||
| a normal install: | ||
| `--deintegrate` converts an app whose Podfile has pods that are not part of |
There was a problem hiding this comment.
it might be confusing that we have two different behaviors for spm --deintegrate and spm.
I understand that we would like to remove a step (manually calling --deintegrate) but perhaps it is not a bad thing that the user has to call it themselves.
There was a problem hiding this comment.
Done. Plain spm no longer runs --deintegrate. On a CocoaPods app it stops and points to spm add --deintegrate.
| [local native module](#local-native-modules). The pods stay in `Podfile.bak`, | ||
| and `spm deinit` restores the Podfile. | ||
|
|
||
| To keep a CocoaPods-only pod next to SwiftPM, convert by hand: |
There was a problem hiding this comment.
are there Cocoapods-only pods? I believe that most Native iOS libraries nowadays supports Swift PM. For React Native packages, we should strive to auto-convert them or to fail. If the users have a pod that still needs to be converted to SwiftPM and that can't be converted automatically, I would probably fail and ask them to open a PR/Issue to convert the library.
There was a problem hiding this comment.
I kept this a warning, as in my top-level comment (point 4). The Podfile now stays unchanged, so spm deinit and then pod install bring those pods back. An error would also block RNTester: its Podfile has local pods that SwiftPM provides through spmModules.
| return true; | ||
| } | ||
|
|
||
| function restorePodfile(appRoot /*: string */) /*: void */ { |
There was a problem hiding this comment.
The move isn't recorded in the marker (unlike the .gitignore lines), so deinit renames any Podfile.bak to Podfile, including one the user made themselves. This goes away if we keep the Podfile in place.
There was a problem hiding this comment.
Gone. The Podfile.bak code is removed.
| return false; | ||
| } | ||
| if (podfileHasThirdPartyPods(appRoot)) { | ||
| if (podfileHasThirdPartyPods(appRoot) || hasPodfileBackup(appRoot)) { |
There was a problem hiding this comment.
When Podfile.bak exists, this skips without saying why. The fail-fast check then suggests spm add --deintegrate, which refuses for the same reason. Could we log the reason here?
If we get rid of the Podfile.bak, we can simplify the code significantly
There was a problem hiding this comment.
Gone. The Podfile.bak code is removed.
| function podfileHasThirdPartyPods(appRoot /*: string */) /*: boolean */ { | ||
| // The names of the pods the Podfile declares explicitly (`pod '...'` or | ||
| // `pod('...')`), deduplicated, in order. React Native itself comes in through | ||
| // use_react_native!, so every name here is a pod that is not part of it. |
There was a problem hiding this comment.
Older or brownfield Podfiles can list RN pods explicitly (e.g. pod 'React-Core', :path => '../node_modules/react-native/'), so this would report them as non-RN. Pods from test targets also end up in the list.
There was a problem hiding this comment.
Done. Pods with :path or :podspec into node_modules/react-native now count as React Native pods. Pods in test targets stay in the list: to find them, we would need to parse the Ruby target blocks.
| const podNames = podfilePodNames(appRoot); | ||
| if (podNames.length > 0) { | ||
| logError( | ||
| 'Your Podfile declares pods that are not part of React Native: ' + |
There was a problem hiding this comment.
Nit: this is a warning, but it goes through logError and the run continues. If we decide to fail here instead (see my comment in the docs), this is the place to do it.
There was a problem hiding this comment.
Done. It is now printed as a warning.
| function runDeintegrate(appRoot /*: string */) /*: void */ { | ||
| const podfilePath = path.join(appRoot, 'Podfile'); | ||
| const backupPath = path.join(appRoot, 'Podfile.bak'); | ||
| if (refuseToOverwritePodfileBackup(appRoot)) { |
There was a problem hiding this comment.
Nit: main already runs this check before we get here, so this one never fires on that path. Is it only meant for direct callers?
There was a problem hiding this comment.
Gone. This check was part of the Podfile.bak code.
|
Thanks for the review, Riccardo! I agree with most of it. My plan:
I will push these as new commits. |
Review feedback: `spm add --deintegrate` no longer moves the Podfile to Podfile.bak. It runs `pod deintegrate` and keeps the Podfile unchanged, so `spm deinit` and then `pod install` go back to CocoaPods. - Plain `spm` no longer converts a CocoaPods app; it points to `spm add --deintegrate`. - The React Native CocoaPods scripts stop `pod install` before any write when the .xcodeproj next to the Podfile has .spm-injected.json. Otherwise `pod install` rewrites the generated Package.swift with a wrong path and Xcode fails to resolve packages before any build phase runs. - The uncommitted-changes prompt comes before `pod deintegrate`. - .gitignore is written only after a successful injection. - Pods sourced from node_modules/react-native count as React Native pods, and the other-pods message is a warning. - The SwiftPM iOS workflows check for `Pods-` references in the pbxproj instead of the Podfile content.
spm deinit take an app back to CocoaPodsspm deinit can take an app back to CocoaPods
spm deinit can take an app back to CocoaPodsspm deinit take an app back to CocoaPods
|
Pushed the changes in 0640f08. One change from my earlier plan (point 1): a mistaken |
| for (const line of fs.readFileSync(podfilePath, 'utf8').split('\n')) { | ||
| // `#{` is Ruby string interpolation, not a comment. A line that ends in | ||
| // `,` continues on the next one. | ||
| const code = continued + line.split(/#(?!\{)/)[0].trimEnd(); |
There was a problem hiding this comment.
Small edge case: continued ends with a space, so if a blank or comment-only line sits in the middle of a multi-line pod call, code.endsWith(',') is false and the statement ends before its :path. For example:
pod 'React-Core',
# comment
:path => '../node_modules/react-native/'This gets reported as a non-RN pod. Trimming the whole code string should fix it.
There was a problem hiding this comment.
Good catch, fixed in dae2c23. The whole joined line is trimmed now, so a blank or comment line inside a pod call no longer ends it. I added a test with both cases.
| ); | ||
| } | ||
| if (fs.existsSync(path.join(appRoot, 'Podfile'))) { | ||
| log('To go back to CocoaPods, run `pod install`.'); |
There was a problem hiding this comment.
Nit: this is printed even when deinit found nothing to remove. Also, when the command was redirected from the JS root, it might help to say where to run it (e.g. cd ios && pod install, or displayPath(appRoot)).
There was a problem hiding this comment.
Fixed in dae2c23. The line is printed only when deinit removed the SwiftPM setup. When the command was run from the JS root, it says cd ios && pod install. Tests added for both.
- A multi-line `pod` call with a blank or comment line in the middle no longer ends early, so a pod sourced from node_modules/react-native is not reported as a non-React Native pod. - `spm deinit` says to run `pod install` only when it removed the SwiftPM setup, and says `cd ios && pod install` when run from the JS root.
cipolleschi
left a comment
There was a problem hiding this comment.
Thanks for addressing all the feedback!
|
@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D129064536. |
|
This pull request was successfully merged by @chrfalch in b255245 When will my fix make it into a release? | How to file a pick request? |
`spm add --deintegrate` keeps the Podfile since #59368, so the RNTester check now looks for CocoaPods references in RNTesterPods.xcodeproj, as the SwiftPM workflows on main do.



Summary:
An app converted to SwiftPM cannot go back to CocoaPods today. The conversion deletes the React Native lines from the Podfile, one line at a time. A call that spans several lines, such as
use_react_native!(…), keeps its other lines, so the Podfile is no longer valid Ruby.This PR keeps the Podfile unchanged. The way back to CocoaPods is then
npx react-native spm deinit, followed bypod install.What changes:
spm add --deintegratekeeps the Podfile. It runspod deintegrateand nothing else on the Podfile.npx react-native spmno longer converts a CocoaPods app. It stops and points tospm add --deintegrate.pod installstops on a SwiftPM app. The Podfile is still there, so a user can runpod installby mistake. That would add CocoaPods back and break the generatedPackage.swift. The React Native CocoaPods scripts now stop with an error, before they write any file, when the.xcodeprojhas the SwiftPM marker file (.spm-injected.json).spm deinitcleans up after itself. It removes the.gitignorelines that the conversion added, and the emptyReactNativeSPMfolders. Then it says to runpod install.Smaller fixes from the review:
pod deintegrate, so "no" changes nothing..gitignoreis written only after a successful conversion.node_modules/react-nativeno longer show in the "pods that are not part of React Native" warning.The three SwiftPM iOS CI workflows checked the Podfile content after the conversion. They now check that the
.xcodeprojhas no CocoaPods references.Where to look:
scripts/setup-apple-spm.js: the conversion anddeinit(items 1, 2, 4).scripts/cocoapods/utils.rb(error_if_spm_injected), called fromreact_native_pods.rbandcocoapods/autolinking.rb: thepod installcheck (item 3).Changelog:
[IOS] [CHANGED] -
react-native spm add --deintegratekeeps the Podfile, andpod installstops with an error on an app that uses SwiftPM.react-native spm deinitand thenpod installtake an app back to CocoaPods.Test Plan:
Unit tests:
25 suites, 1236 tests pass.
A new app on React Native 0.89.0-nightly-20261021-c7429b0e9 and CLI 21.0.0-alpha.1, with the changed scripts copied in:
npx react-native spm add --deintegrate --yes: the app converts, the Podfile is unchanged, and the SwiftPM build succeeds.pod install: it stops with "This app uses Swift Package Manager for React Native … sopod installwould break the Xcode project." No file changes.npx react-native run-iosandbuild-ios: both build the app. The CLI sees the marker file and does not runpod install.npx react-native spm deinit, thenpod install: the CocoaPods build succeeds.Not checked: Flow locally (my Flow binary is too old),
run-ios --force-pods, and CLI 20.